/*
 * Copyright 2009 Andreas Borglin andreas.borglin@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package borge.mobile.lib.pimp;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Vector;
import java.util.Stack;

/**
 * PreviewHandler extends BaseHandler
 * with specific code for scaled image shown in device.
 * @author Andreas Borglin
 */
final class PreviewHandler extends BaseHandler {
    
    private Vector operations;
    private double scaleRatio;
    private double resizedScaleRatio;
    private Stack resizedScaleRatios;
    private Stack resizedSizes;
    private int overlayCount;
    private String lastOverlayURL;
    private int origWidth;
    private int origHeight;
    private int areaWidth;
    private int areaHeight;
    private PerformedOperation resizeOperation;

    private ImageEffectProvider overlayProvider;
    
    private class Pair{
        public Pair(int w, int h){
            width = w;
            height = h;
        }
        public int width;
        public int height;
    }
    
    
    protected PreviewHandler() {
        super(true,true);
        operations = new Vector();
        resizedScaleRatios = new Stack();
        resizedSizes = new Stack();
    }

    /** Initialize the MediaProcessor and create controls */
    protected void initialize(String file)
            throws ImageEffectException {
        
        super.initialize(file, new ImageOutputStream());
        imageSupport = getSupportInfo();

    }
    
    protected void scaleImage(int areaWidth, int areaHeight) throws ImageEffectException{
        try{
            scaleInputImage(areaWidth, areaHeight);
        }
        catch(Exception e){
            throw new ImageEffectException(e.toString());
        }
    }
    
    protected String getInputFile(){
        return inputFile;
    }
    
    protected void postOperation(PerformedOperation po){
        
        operations.addElement(po);
        
    }
    
    protected Vector getOperations(){
        if(resizeOperation != null)
            operations.addElement(resizeOperation);
        return operations;
    }
    
    protected void reset(){
        super.reset();
        operations.removeAllElements();
        System.gc();
    }
    
    protected boolean undo(){
        
        boolean b = super.undo();
        
        // If RESIZE, we handle scale ratio and stuff
        undoResize(operations);
        
        if(b && operations.size() > 0){
            operations.removeElementAt(operations.size()-1);   
        }
        
        return b;
        
    }
    
    private void undoResize(Vector ops){
        
        try{
            if(ops.size() > 0){
                PerformedOperation po = (PerformedOperation)ops.elementAt(ops.size()-1);
                if (po.operation == RESIZE) {
                    resizedScaleRatios.pop();
                    resizedSizes.pop();
                }
            }

        }
        catch(Exception e){
            // ignore
            e.printStackTrace();
        }
        
    }
    
   /** Scale image according to a scaleRatio ratio */
    private void scaleOverlayImage() throws Exception {
        
        int imagew = overlayProvider.getImageWidth();
        int imageh = overlayProvider.getImageHeight();

        int neww = (int) (imagew * scaleRatio);
        int newh = (int) (imageh * scaleRatio);

        overlayProvider.performOperation(RESIZE, neww,newh, NO_VALUE, NO_VALUE);
        overlayProvider.applyChanges();

    }
    
    /** Scale image to fit within screen width and height */
    private void scaleInputImage(int cw, int ch) throws Exception {
        
        width = effectProvider.getImageWidth();
        height = effectProvider.getImageHeight();
        origWidth = width;
        origHeight = height;
        areaWidth = cw;
        areaHeight = ch;
        
        
        // If no scaling is needed
        if (width < cw && height < ch) {
            scaleRatio = 1.0; // preview is 100% of real
            return;
        }
        
        // TODO: Replace with a smarter algorithm
        
        double scaleFactor;

        int larger = width;
        int smaller = height;
        int largeSide = cw;
        int smallSide = ch;

        if ((width - cw) < (height - ch)) {
            larger = height;
            smaller = width;
            largeSide = ch;
            smallSide = cw;
        }

        scaleFactor = (double) larger / (double) smaller;
        int scaleSmall = (int) (largeSide / scaleFactor);
        int scaleLarge = largeSide;

        if (scaleSmall > smallSide) {
            scaleSmall = smallSide;
            scaleLarge = (int) (scaleSmall * scaleFactor);
        }


        scaleRatio = (((double) scaleLarge / (double) larger));
        
        if (largeSide == cw) {
            effectProvider.scaleAndRotate(scaleLarge,scaleSmall,0);
        } 
        else {
            effectProvider.scaleAndRotate(scaleSmall,scaleLarge,0);
        }

        applyChanges();
        
    }
    
        /** Return the scale ratio as scaleRatio */
    protected int getScaleRatioAsPercent(){
        
        if(!resizedScaleRatios.empty())
            return (int)(((Double)resizedScaleRatios.peek()).doubleValue() * 100.0);
        
        return (int)(scaleRatio * 100.0);
        
    }
    
    protected double getResizedScaleRatio(){
        if(!resizedScaleRatios.empty())
            return ((Double)resizedScaleRatios.peek()).doubleValue();
        
        return getScaleRatio();
    }
    
    protected double getScaleRatio(){
        return scaleRatio;
    }
    
    protected OverlayBounds createAndScaleOverlayImage(String imageURL) throws ImageEffectException{
        
        try{
            // call preview to scale image and save it
            overlayProvider = new JSR234Provider(false);
            providerInit(overlayProvider, imageURL, new ImageOutputStream());
            scaleOverlayImage();
            lastOverlayURL = imageURL;
            return new OverlayBounds(overlayProvider.getImageWidth(), overlayProvider.getImageHeight());
        }
        catch(Exception e){
            throw new ImageEffectException("Failed to create overlay image: " + e.toString());
        }
        
       
    }
    
    protected void addOverlayImage(int x, int y) throws ImageEffectException{

        if(overlayProvider == null)
            throw new ImageEffectException("Overlay must be created first.");
        
        InputStream is = new ByteArrayInputStream(overlayProvider.getCurrentImageData());
        
        effectProvider.addOverlayImage(is, x, y, overlayCount);
        int rx = calcRealValue(x);
        int ry = calcRealValue(y);
        postOperation(new PerformedOperation(OVERLAY,new int[]{rx,ry},lastOverlayURL));

        lastOverlayURL = null;
        overlayProvider = null;
    }

    protected boolean handleRotate(int rotation) throws ImageEffectException {

        // Only need to handle 90 and 270
        if(rotation == ROTATE_180)
            return false;

        // switch width and height
        int rotW = getImageHeight();
        int rotH = getImageWidth();

        // If rotated image fits on screen
        if(rotW < areaWidth && rotH < areaHeight){
            return false;
        }


        // we know that image fit before, so we only need to check if
        // old width fits on new height

        // If, we need to rescale
        if (rotH > areaHeight) {
            double scale = (double)areaHeight / (double)rotH;
            rotH = areaHeight;
            rotW = (int)((double)rotW * scale);
        }

        effectProvider.rotateAndScale(rotation, rotW, rotH);

        return true;
    }
    
    // TODO: Merge resize methods...
    protected void resize(int percent) throws ImageEffectException {
        
        checkState();
        if(percent < 1 || percent > 99)
            throw new ImageEffectException("Resizing only allowed in range 1 - 99 %");
        
        double scale = (double)percent / 100.0;
        
        // Change of plans...let's always keep preview image in full screen
        // and only change ratio percentage so it can be > 100%
        resizedScaleRatio = getResizedScaleRatio() / scale;
        resizedScaleRatios.push(new Double(resizedScaleRatio));
        int width = (int)(getResizedRealImageWidth() * scale);
        int height = (int)(getResizedRealImageHeight() * scale);
        resizedSizes.push(new Pair(width, height));

        lastOpResize = true;
        if(resizeOperation == null)
            resizeOperation = new PerformedOperation(RESIZE,new int[]{width, height});
        else{
            resizeOperation.values[0] = width;
            resizeOperation.values[1] = height;
        }
        
    }
    
    // Special resize for preview image
    protected void resize(int newWidth, int newHeight, boolean keepAspectRatio) throws ImageEffectException {
        
        checkState();
        if(newWidth < 0 || newHeight < 0)
            throw new ImageEffectException("Width and height must be greater than 0");
        
        // Calculate scale change in percent..
        int wScale = (int)(((double)newWidth / (double)getResizedRealImageWidth()) * 100);
        int hScale = (int)(((double)newHeight / (double)getResizedRealImageHeight()) * 100);
        
        if(keepAspectRatio && wScale != hScale)
            throw new ImageEffectException("Aspect ratio can not be changed");
        
        // Change of plans...let's always keep preview image in full screen
        // and only change ratio percentage so it can be > 100%
        resizedScaleRatio = getResizedScaleRatio() / wScale;
        resizedScaleRatios.push(new Double(resizedScaleRatio));
        resizedSizes.push(new Pair(newWidth, newHeight));
        
        lastOpResize = true;
        //postOperation(new PerformedOperation(RESIZE,new int[]{wScale,hScale}));
        if(resizeOperation == null)
            resizeOperation = new PerformedOperation(RESIZE,new int[]{newWidth, newHeight});
        else{
            resizeOperation.values[0] = newWidth;
            resizeOperation.values[1] = newHeight;
        }
        
        
    }
    
    protected int getResizedRealImageWidth(){
        
        if(resizedSizes.empty())
            return origWidth;
        else
            return ((Pair)resizedSizes.peek()).width;
        
    }
    
    protected int getResizedRealImageHeight(){
        
        if(resizedSizes.empty())
            return origHeight;
        else
            return ((Pair)resizedSizes.peek()).height;
    }
    
    protected void validateOutputFormat(String format) throws ImageEffectException {
        
        if(format == null)
            return;
        
        String[] formats = getSupportedOutputFormats();
        for (int i = 0; i < formats.length; ++i) {

            if (formats[i].equalsIgnoreCase(format)) {
                return;
            }
        }

        throw new ImageEffectException("Format: " + format + " not supported");
    }
    
    protected int calcRealValue(int scaledValue){
        
        // The value (x,y,width,height) we get here is from preview image
        // We must convert them to fit into current real size
        double curRatio = getResizedScaleRatio();
        return (int) (scaledValue / curRatio);
    }
    
    protected boolean changesMade(){
        return operations.size() > 0 || !resizedSizes.empty();
    }
    
}
